/**
 * Layer to display surfaces using SDL.
 */

#ifdef HAVE_SDL

#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <SDL.h>

#include <visual.h>
#include <visual_sdl.h>
#include <coord.h>
#include <debug.h>
#include <utils.h>

static SDL_Surface *screen = NULL;


static struct visual_ops sdl_layer_ops = {
  .quit    = visual_sdl_quit,
  .init    = visual_sdl_init,
  .update  = visual_sdl_update,
  .waitkey = visual_sdl_waitkey,
  .clear   = visual_sdl_clear,
  .fill    = visual_sdl_fill,
  .display = visual_sdl_display,
  .save    = visual_sdl_save,
};


/**
 * Change visual layer to use SDL.
 */
void
visual_change_layer_sdl( void )
{
  visual_change_layer( &sdl_layer_ops );
}


/**
 * Quit engine.
 */
void
visual_sdl_quit( void )
{
  SDL_Quit();
}

/**
 * Initialize video device using the given dimensions.
 *
 * It ouput video information using info(), see debug.h to set the right
 * debug_level to see this info.
 *
 * @param w screen width
 * @param h screen height
 *
 * @return 0 on succes, error otherwise.
 */
int
visual_sdl_init( uint16_t w, uint16_t h )
{
  if ( SDL_Init( SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE ) != 0)
    {
      err( "Could not initialize SDL: %s\n",  SDL_GetError() );
      return -1;
    }

  atexit( visual_sdl_quit );


  const SDL_VideoInfo *i = SDL_GetVideoInfo();
  if ( i == NULL )
    return -1;

  /* Print out video info */
  info( "Video Information:\n" );
  info( "   Hardware Acceleration:\n" );
  info( "      available......? %s\n", i->hw_available ? "Yes" : "No" );
  info( "      blit...........? %s\n", i->blit_hw ? "Yes" : "No" );
  info( "      blit color key.? %s\n", i->blit_hw_CC ? "Yes" : "No" );
  info( "      blit alpha.....? %s\n", i->blit_hw_A ? "Yes" : "No" );
  info( "   Software Acceleration:\n" );
  info( "      blit...........? %s\n", i->blit_sw ? "Yes" : "No" );
  info( "      blit color key.? %s\n", i->blit_sw_CC ? "Yes" : "No" );
  info( "      blit alpha.....? %s\n", i->blit_sw_A ? "Yes" : "No" );
  info( "\n" );
  info( "   blit_fill? %s\n", i->blit_fill ? "Yes" : "No" );
  info( "   video_mem: %d Kb\n", i->video_mem );
  info( "\n" );

  SDL_PixelFormat *pf = i->vfmt;
  info( "Best Pixel Format:\n" );
  info( "   BitsPerPixel: %d (bytes: %d)\n",
	pf->BitsPerPixel, pf->BytesPerPixel );

  uint8_t bpp = 2 << sizeof(color_t);

  screen = SDL_SetVideoMode( w, h, bpp, SDL_SWSURFACE );

  if ( screen == NULL )
    {
      err( "Could not create screen %dx%d @ %dbpp.\n", w, h, bpp );
      return -1;
    }

  info( "Allocated screen %dx%d @ %dbpp.\n", w, h, bpp );

  return 0;
}

/**
 * Update screen.
 *
 * @return 0 on success, error otherwise.
 */
int
visual_sdl_update( void )
{
  if ( screen == NULL )
    return -1;
  return SDL_Flip( screen );
}

/**
 * Wait for key and return key symbol as described in SDLKey(3).
 *
 * It also handle program exit (SDL_QUIT) and do screen refreshes when
 * required (SDL_ACTIVEEVENT).
 *
 * @return key symbol or 0 on error.
 */
int
visual_sdl_waitkey( void )
{
  SDL_Event e;
  while ( 1 )
    {
      SDL_WaitEvent( &e );
      switch ( e.type )
	{
	case SDL_KEYDOWN:
	  /* ignore keydown */
	  break;

	case SDL_KEYUP:
	  return e.key.keysym.sym;
	  break;

	case SDL_ACTIVEEVENT:
	  visual_sdl_update();
	  break;

	case SDL_QUIT:
	  exit( 0 );
	  break;

	default:
	  break;
	}
    }
  return 0;
}


/**
 * Clear (blanks) screen.
 *
 * @return 0 on success, error otherwise.
 */
int
visual_sdl_clear( void )
{
  if ( screen == NULL )
    return -1;
  return SDL_FillRect( screen, NULL, 0 );
}


/**
 * Fill rectangular screen region with the given color.
 *
 * @param color what color to paint.
 * @param rectan
 *
 * @return 0 on success, error otherwise.
 */
int
visual_sdl_fill( color_t color, rect_t rect )
{
  if ( screen == NULL )
    return -1;

  SDL_Rect r = { rect.p0.x, rect.p0.y,
		 rect.p1.x - rect.p0.x,
		 rect.p1.y - rect.p0.y };

  return SDL_FillRect( screen, &r,
		       SDL_MapRGBA( screen->format,
				    color.r, color.g, color.b, color.a ) );
}


/**
 * Display the surface at the given position
 *
 * @param surface what to display
 * @param pos where to display
 *
 * @return 0 on success, error otherwise.
 */
int
visual_sdl_display( surface_t *surface, coord_t pos )
{
  if ( screen == NULL )
    return -1;

  /* Check if it's outside screen */
  if ( ( pos.x > screen->w ) || ( pos.y > screen->h ) )
    return -1;

  /* Limit drawing to inside screen */
  uint16_t x_max = min( surface->size.x, (uint16_t)screen->w );
  uint16_t y_max = min( surface->size.y, (uint16_t)screen->h );

  /* Ensure SDL surface is same depth as our color_t */
  uint_fast8_t bpp = sizeof(color_t);
  assert( bpp == screen->format->BytesPerPixel );

  /* setup pointers */
  color_t *src = &(PIXEL_AT( surface, 0, 0 ));
  color_t *dst = &((color_t*)screen->pixels)[ \
    QUAD_TO_LIN( screen->w, pos.x, pos.y ) ];


  uint16_t dy = y_max - pos.y;
  uint16_t dx = x_max - pos.x;
  uint16_t dx_bytes = dx * bpp;

  /* copy src to dst */
  uint_fast16_t i;
  for ( i=0; i < dy; i++ )
    {
      memcpy( dst, src, dx_bytes ); /* this is fast! */
      /* go to next line */
      src = src + surface->size.x;
      dst = dst + screen->w;
    }
  return 0;
}


/**
 * Save the display to a bitmap image.
 *
 * @param filename where to save the file.
 *
 * @return 0 on success, error otherwise.
 */
int
visual_sdl_save( const char *filename )
{
  return SDL_SaveBMP( screen, filename );
}

#endif /* HAVE_SDL */
